using UnityEngine;
using System;
using System.Collections;

public class CharacterBehavior : MonoBehaviour
{
	// states
	CharacterState m_oCurrentState;
	
	// public variables to be accessed in all the states
	public CharacterController oController;
	Transform tCharacter;
	public Transform tCamera;
	public CameraState m_oCamera;
	
	public GUIText m_oGUIText;
	
	#region State management & update
	void Start()
	{	
		m_oCurrentState = GetComponent<CharacterStateStanding>();
		
		oController = GetComponent<CharacterController>();
		tCharacter = GetComponent<Transform>();
		tCamera = GameObject.FindGameObjectWithTag("MainCamera").GetComponent<Transform>();
		m_oCamera = GameObject.FindGameObjectWithTag("MainCamera").GetComponent<CameraState>();
		
		sPhysics.Instance.startPhysics();
	}
	
	public CharacterState getState() { return m_oCurrentState; }
	
	public void changeState<state>()
	{
		Type oType = typeof(state);
		m_oCurrentState.exit();
		m_oCurrentState = (CharacterState)GetComponent(oType.ToString());
		m_oCurrentState.enter();
		
		sCameras.Instance.changeState(oType.ToString());
	}
	
	public void renderLine(Vector3 v1, Vector3 v2)
	{
		Debug.DrawLine(v1, v2, Color.white, 0.5f, false);
	}
	
	void renderDebugStuff()
	{
		Debug.DrawRay(tCharacter.position, tCharacter.up * 3, Color.green, 0.0f, false);
		Debug.DrawRay(tCharacter.position, tCharacter.forward * 3, Color.blue, 0.0f, false);
		Debug.DrawRay(tCharacter.position, tCharacter.right * 3, Color.red, 0.0f, false);
	}
	
	void Update()
	{
		m_oCurrentState.update();
		renderDebugStuff();
		m_vLastPosition = tCharacter.position;
		
		m_oGUIText.text = m_oCurrentState.ToString();
		
		// update shooting timer
		m_fLastShotTimer -= Time.deltaTime;
	}
	#endregion
	
	#region Shared gameplay
	// variables used in several states
	public float fTimerTo = 0.0f;
	public Vector3 vTargetPosition = Vector3.zero;
	public Vector3 vTargetNormal = Vector3.zero;
	public Vector3 vTargetPoint = Vector3.zero;
	
	// last frame data
 	Vector3 m_vLastPosition;
	
	// returns the position of the edge of the collision, using iIterations raycasts and with a precision of (distanceBetweenPoints * 0.5 * iIterations)
	public Vector3 getCollisionEdge(Vector3 vFrom, Vector3 vTo, Vector3 vRayDirection, float fRayDistance, int iIterations)
	{
		// first ray detects the position of the wall, we will use that plane to check the collision edge
		bool bFound = false;
		Vector3 vDirection = vTo - vFrom;
		float fDistance = vDirection.magnitude;
		vDirection.Normalize();
		Vector3 vMiddle; 
		int iIterationsDone = 0;
		while(!bFound && iIterationsDone < iIterations)
		{
			// will perform a raycast in the middle position between from and to
			vMiddle = vFrom + vDirection * (fDistance * (float)Math.Pow(0.5f, iIterations));
			// if it collides we need to check the next part, so now the from position is the previous middle position
			if (sPhysics.Instance.linecastVsAll(vMiddle, vMiddle + vRayDirection * fRayDistance))
			{
				vFrom = vMiddle;
			}
			renderLine(vMiddle, vMiddle + vRayDirection * fRayDistance);
			// if didnt collide, the from position is the same but the middle position will be calculated at current distance / 2 in the next iteration
			
			++iIterationsDone;
		}
		return vFrom;
	}
	
	public Vector3 getCollisionEdgeAvoidingGaps(Vector3 vFrom, Vector3 vTo, Vector3 vRayDirection, float fRayDistance, int iIterations, Vector3 vGapDirection, float _fGap, ref bool bGap)
	{
		Vector3 res = getCollisionEdge(vFrom, vTo, vRayDirection, fRayDistance, iIterations);
		vFrom = res + vGapDirection * _fGap;
		vTo = vFrom + vRayDirection * fRayDistance;
		bGap = sPhysics.Instance.linecastVsAll(vFrom, vTo);
		renderLine(vFrom, vTo);
		return res;
	}
	
	const float m_fGravity = -9.8f;
	float m_fYAcceleration = 0.0f;
	bool m_bInGround = true;
	public Vector3 vGroundNormal;
	public void updateVerticalPosition()
	{
		Vector3 p1 = getMiddlePos();
		Vector3 p2 = getPos() + Vector3.down * 0.2f;
		m_bInGround = sPhysics.Instance.linecastVsAll(p1, p2);
		if (m_bInGround)
		{
			m_fYAcceleration = 0.0f;
			setPos(sPhysics.Instance.getLinecastPoint());
			vGroundNormal = sPhysics.Instance.getLinecastNormal();
		}
		else
		{
			m_fYAcceleration += m_fGravity * Time.deltaTime;
			oController.Move(new Vector3(0.0f, m_fYAcceleration * Time.deltaTime, 0.0f));
		}
	}
	
	public Vector3 getGroundDirection(Vector3 vDirection, Vector3 vGroundNormal)
	{
		Vector3 right = -Vector3.Cross(vDirection, Vector3.up);
		return Vector3.Cross(right, vGroundNormal);
	}
	
	public Vector3 getGroundOfPoint(Vector3 vPosition)
	{
		if (sPhysics.Instance.linecastVsAll(vPosition + Vector3.up, vPosition + Vector3.down * 10.0f))
		{
			return sPhysics.Instance.getLinecastPoint();
		}
		return vPosition;
	}

	public bool updateShotMode()
	{
		bool bValue = Input.GetAxis("LT") > 0.5f;
		return bValue;
	}
	
	float m_fLastShotTimer = 0.0f;
	public float SHOT_TIME = 0.25f;
	public bool isShooting()
	{
		bool bValue = Input.GetAxis("RT") > 0.5f;
		return bValue && m_fLastShotTimer < 0.0f;
	}
	
	public bool hasMoved()
	{
		return ((m_vLastPosition - tCharacter.position).magnitude / Time.deltaTime) > 0.05f;
	}
	#endregion
	
	#region Getters / setters
	public void setPos(Vector3 _vPos)
	{
		tCharacter.position = _vPos;
	}
	
	public void setForward(Vector3 _v, Vector3 _vUp)
	{
		tCharacter.setForward(_v, _vUp);
	}
	
	public Vector3 getPos()
	{
		return tCharacter.position;
	}
	
	public Vector3 getMiddlePos()
	{
		return tCharacter.position + Vector3.up;
	}
	
	public Vector3 getForward()
	{
		return tCharacter.forward;
	}
	
	public Vector3 getRight()
	{
		return tCharacter.right;
	}
	#endregion
}